Skip to content

Postgres: Support XML functions (XMLELEMENT, XMLPI, XMLROOT, XMLSERIALIZE, XMLEXISTS) - #2507

Open
BenSatori wants to merge 3 commits into
apache:mainfrom
BenSatori:postgres-xml-functions
Open

BenSatori wants to merge 3 commits into
apache:mainfrom
BenSatori:postgres-xml-functions

Conversation

@BenSatori

Copy link
Copy Markdown
Contributor

Adds support for PostgreSQL XML function syntaxes:

  • XMLELEMENT(NAME name [, XMLATTRIBUTES(...) ] [, content [, ...]])
  • XMLPI(NAME name [, content ])
  • XMLROOT(xml, VERSION {text | NO VALUE} [, STANDALONE {YES | NO | NO VALUE} ])
  • XMLSERIALIZE({ DOCUMENT | CONTENT } value AS type [ [ NO ] INDENT ])
  • XMLEXISTS(text PASSING [BY {REF|VALUE}] xml [BY {REF|VALUE}])

Example

SELECT XMLELEMENT(NAME foo, 'bar'), * FROM customers;
SELECT XMLELEMENT(NAME foo, XMLATTRIBUTES('v' AS attr), 'bar');
SELECT XMLROOT('<a/>'::xml, VERSION '1.0');
SELECT XMLSERIALIZE(DOCUMENT '<a/>'::xml AS TEXT);
SELECT XMLEXISTS('/a' PASSING BY REF '<a/>');

Docs:

AI Assistance: Code authored and validated with GitHub Copilot.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.93233% with 36 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.93%. Comparing base (9ae00e7) to head (207ddcf).

Files with missing lines Patch % Lines
src/parser/mod.rs 74.33% 5 Missing and 24 partials ⚠️
src/ast/query.rs 69.23% 2 Missing and 2 partials ⚠️
src/ast/spans.rs 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2507      +/-   ##
==========================================
- Coverage   80.96%   80.93%   -0.04%     
==========================================
  Files          42       42              
  Lines       33359    33481     +122     
  Branches    33359    33481     +122     
==========================================
+ Hits        27009    27097      +88     
- Misses       2790     2799       +9     
- Partials     3560     3585      +25     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/ast/mod.rs
Comment on lines 115 to 116
XmlNamespaceDefinition, XmlPassingArgument, XmlPassingClause, XmlTableColumn,
XmlTableColumnOption,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should export the new enum next to XmlPassingArgument.

Suggested change
XmlNamespaceDefinition, XmlPassingArgument, XmlPassingClause, XmlPassingMechanism,
XmlTableColumn, XmlTableColumnOption,

Comment thread src/parser/mod.rs
Comment on lines 17171 to 17205

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should consume BY only together with REF or VALUE, as a lone BY is currently dropped, so XMLEXISTS('/a' PASSING '<a/>' BY) parses and renders without it, while PostgreSQL rejects it.

Suggested change
let mechanism = self.parse_optional_xml_passing_mechanism();
let expr = self.parse_expr()?;
let alias = if self.parse_keyword(Keyword::AS) {
Some(self.parse_identifier()?)
} else {
None
};
let trailing_mechanism = self.parse_optional_xml_passing_mechanism();
arguments.push(XmlPassingArgument {
expr,
alias,
mechanism,
trailing_mechanism,
});
if !self.consume_token(&Token::Comma) {
break;
}
}
}
Ok(XmlPassingClause { arguments })
}
fn parse_optional_xml_passing_mechanism(&mut self) -> Option<XmlPassingMechanism> {
if self.parse_keywords(&[Keyword::BY, Keyword::REF]) {
Some(XmlPassingMechanism::ByRef)
} else if self.parse_keywords(&[Keyword::BY, Keyword::VALUE]) {
Some(XmlPassingMechanism::ByValue)
} else {
None
}
}

Comment thread src/parser/mod.rs
/// Parse the argument list of `XMLEXISTS(text PASSING [BY {REF|VALUE}] xml [BY {REF|VALUE}])`.
fn parse_xmlexists_argument_list(&mut self) -> Result<FunctionArgumentList, ParserError> {
let xpath_expr = self.parse_expr()?;
let passing = self.parse_xml_passing_clause()?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should require PASSING, which parse_xml_passing_clause treats as optional. SELECT XMLEXISTS('/a') currently parses and renders as SELECT XMLEXISTS('/a' ), while PostgreSQL rejects it.

Suggested change
let passing = self.parse_xml_passing_clause()?;
if !self.peek_keyword(Keyword::PASSING) {
return self.expected_ref("PASSING", self.peek_token_ref());
}
let passing = self.parse_xml_passing_clause()?;

Comment thread tests/sqlparser_common.rs
dialects.verified_stmt("SELECT XMLSERIALIZE(DOCUMENT '<a/>'::xml AS TEXT NO INDENT)");
dialects.verified_stmt("SELECT XMLEXISTS('/a' PASSING BY REF '<a/>')");
dialects.verified_stmt("SELECT XMLEXISTS('/a' PASSING '<a/>')");
dialects.verified_stmt("SELECT XMLEXISTS('/a' PASSING BY VALUE '<a/>')");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the red tests I found and mentioned in the other notes.

Suggested change
dialects.verified_stmt("SELECT XMLEXISTS('/a' PASSING BY VALUE '<a/>')");
dialects.verified_stmt("SELECT XMLEXISTS('/a' PASSING BY VALUE '<a/>')");
dialects.verified_stmt("SELECT XMLEXISTS('/a' PASSING BY VALUE '<a/>' BY REF)");
for sql in [
"SELECT XMLEXISTS('/a')",
"SELECT XMLEXISTS('/a' PASSING '<a/>' BY)",
] {
assert!(dialects.parse_sql_statements(sql).is_err(), "{sql}");
}

@LucaCappelletti94 LucaCappelletti94 added the waiting on contributor The review needs further refinements by its author label Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PostgreSQL waiting on contributor The review needs further refinements by its author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants